/*
 * Copyright 2008, Freescale Semiconductor, Inc
 * Andy Fleming
 *
 * Based vaguely on the Linux code
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <config.h>
#include <common.h>
#include <command.h>
#include <mmc.h>
#include <part.h>
#include <malloc.h>
#include <linux/list.h>
#include <div64.h>
#include <oem_func.h>

//static int cur_dev_num = -1;

static const unsigned int tran_exp[] = {
        10000,          100000,         1000000,        10000000,
        0,              0,              0,              0
};
 
static const unsigned char tran_mant[] = {
        0,      10,     12,     13,     15,     20,     25,     30,
        35,     40,     45,     50,     55,     60,     70,     80,
};

static const unsigned int tacc_exp[] = {
        1,      10,     100,    1000,   10000,  100000, 1000000, 10000000,
};

static const unsigned int tacc_mant[] = {
       0,      10,     12,     13,     15,     20,     25,     30,
       35,     40,     45,     50,     55,     60,     70,     80,
};

int mmc_init_devdesc(struct mmc *mmc)
{
	mmc->block_dev.lun = 0;                                                               
        mmc->block_dev.type = 0;                                                              
        mmc->block_dev.blksz = mmc->read_bl_len;                                              
        mmc->block_dev.lba = mmc->sector_count;                                               
        sprintf(mmc->block_dev.vendor, "Man %06x Snr %08x", mmc->cid[0] >> 8,                 
             (mmc->cid[2] << 8) | (mmc->cid[3] >> 24));                                    
        sprintf(mmc->block_dev.product, "%c%c%c%c%c", mmc->cid[0] & 0xff,                     
         (mmc->cid[1] >> 24), (mmc->cid[1] >> 16) & 0xff,                              
       (mmc->cid[1] >> 8) & 0xff, mmc->cid[1] & 0xff);                               
        sprintf(mmc->block_dev.revision, "%d.%d", mmc->cid[2] >> 28,                          
          (mmc->cid[2] >> 24) & 0xf);                                                   
      init_part(&mmc->block_dev);                                                           
                                                                                           
        return 0; 
}

int mmc_calculate_dtr(int m,int e)
{
	return (tran_exp[e] * tran_mant[m]);		
}
int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
{
	return mmc->send_cmd(mmc, cmd, data);
}
int mmc_send_app_op_cond(struct mmc* mmc, u32 *rocr)
{               
      struct mmc_cmd cmd;

      memset(&cmd, 0, sizeof(struct mmc_cmd));

      cmd.cmdidx = SD_CMD_APP_SEND_OP_COND;
      cmd.cmdarg = 0;
      cmd.flags =  MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;

      return mmc_send_cmd(mmc, &cmd,(struct mmc_data *)cmd.response);
}
int mmc_send_cid(struct mmc* mmc)
{
	struct mmc_cmd cmd;

	 cmd.cmdidx = MMC_CMD_ALL_SEND_CID;                                                   
         cmd.resp_type = MMC_RSP_R2;                                                          
         cmd.cmdarg = 0;                                                                      
         cmd.flags = 0;                                                                       
    if(!mmc_send_cmd(mmc, &cmd, NULL)){                           
         	memcpy(mmc->cid, cmd.response, 16);
		return 0;
	 }else
		printf("failed to get CID\n");
	 return -1;
}
int mmc_into_trans_mode(struct mmc * mmc)
{
	struct mmc_cmd cmd;

	cmd.cmdidx = MMC_CMD_SELECT_CARD;                                                    
        cmd.resp_type = MMC_RSP_R1b;                                                         
        cmd.cmdarg = mmc->rca << 16;                                                         
        cmd.flags = 0; 
	return mmc_send_cmd(mmc, &cmd, NULL);

}
int block_send_csd(struct mmc * mmc)
{
	struct mmc_cmd cmd;
	int err=0;
	
	memset(&cmd, 0, sizeof(struct mmc_cmd));                                          
	cmd.cmdidx = MMC_CMD_SEND_CSD;                                                       
     	cmd.resp_type = MMC_RSP_R2| MMC_CMD_AC;                                                          
      	cmd.cmdarg = mmc->rca << 16;                                                         
        cmd.flags =  MMC_RSP_R2 | MMC_CMD_AC;          //MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
	
	err=mmc_send_cmd(mmc, &cmd,NULL);
	if(err) return err;
#ifdef CONFIG_SD_DEBUG	
	int i=0;                                                                             
	while(i<4){                                                                          
	       printf("%8x ",cmd.response[i++]);                                                     
	}                                                                                    
	       printf("\n");
#endif
	memcpy(mmc->csd,cmd.response, 16);
	return 0;
}
int mmc_set_blocklen(struct mmc *mmc, int len)
{
	struct mmc_cmd cmd;

	cmd.cmdidx = MMC_CMD_SET_BLOCKLEN;
	cmd.resp_type = MMC_RSP_R1;
	cmd.cmdarg = len;
	cmd.flags = 0;

	return mmc_send_cmd(mmc, &cmd, NULL);
}

ulong mmc_write(struct mmc* mmc, ulong start, lbaint_t blkcnt, const void*src)
{
	struct mmc_cmd cmd;
	struct mmc_data data;
	int err;
	int stoperr = 0;
	int blklen;

	blklen = mmc->write_bl_len;

	if (!mmc)
		return -1;
	err = mmc_set_blocklen(mmc, mmc->write_bl_len);                                       
                                                                                        
        if (err) {                                                                            
              printf("set write bl len failed\n\r");                                        
              return err;                                                                   
        } 

	if (blkcnt > 1)
		cmd.cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK;
	else
		cmd.cmdidx = MMC_CMD_WRITE_SINGLE_BLOCK;

	if (mmc->high_capacity)
		cmd.cmdarg = start;
	else
		cmd.cmdarg = start * blklen;

	cmd.resp_type = MMC_RSP_R1;
	cmd.flags = 0;

	data.src = src;
	data.blocks = blkcnt;
	data.blocksize = blklen;
	data.flags = MMC_DATA_WRITE;

	err = mmc_send_cmd(mmc, &cmd, &data);

	if (err) {
		printf("mmc write failed\n\r");
		return err;
	}

	if (blkcnt > 1) {
		cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION;
		cmd.cmdarg = 0;
		cmd.resp_type = MMC_RSP_R1b;
		cmd.flags = 0;
		stoperr = mmc_send_cmd(mmc, &cmd, NULL);
	}

	return blkcnt;
}
int mmc_read_multi(struct mmc *mmc,void *dst,uint secnum,uint blkcnt)
{
	struct mmc_cmd cmd;
	struct mmc_data data;
	
	cmd.cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK;

	if(mmc->high_capacity)
		cmd.cmdarg = secnum;
	else
		cmd.cmdarg = secnum * mmc->read_bl_len;

	cmd.resp_type = MMC_RSP_R1;
	cmd.flags = 0;
	data.dest = dst;
	data.blocks = blkcnt;
	data.blocksize = mmc->read_bl_len;
	data.flags = MMC_DATA_READ;
	
	return mmc_send_cmd(mmc, &cmd, &data);
}

int mmc_read_block(struct mmc *mmc, void *dst, uint blocknum)
{
	struct mmc_cmd cmd;
	struct mmc_data data;

	cmd.cmdidx = MMC_CMD_READ_SINGLE_BLOCK;

	if(oem_progress_update && (blocknum % 4 == 0))
	{
		oem_progress_update(0);
	}

	if (mmc->high_capacity)
	{
	//	printf("[uboot]-sdhci:cmd.cmdarg:%d.\n",blocknum);
		cmd.cmdarg = blocknum;
	}
	else
		cmd.cmdarg = blocknum * mmc->read_bl_len;

	cmd.resp_type = MMC_RSP_R1;
	cmd.flags = 0;

	data.dest = dst;
	data.blocks = 1;
	data.blocksize = mmc->read_bl_len;
	data.flags = MMC_DATA_READ;

	return mmc_send_cmd(mmc, &cmd, &data);
}

int mmc_select_card(struct mmc* mmc)
{
	struct mmc_cmd cmd;
	cmd.cmdidx = MMC_CMD_SELECT_CARD;                                                    
        cmd.resp_type = MMC_RSP_R1b;                                                         
        cmd.cmdarg = mmc->rca << 16;                                                         
        cmd.flags = 0;                                                                       
      	return mmc_send_cmd(mmc, &cmd, NULL); 
}
int mmc_go_idle(struct mmc* mmc)
{
	struct mmc_cmd cmd;
	int err;

	udelay(1000);

	cmd.cmdidx = MMC_CMD_GO_IDLE_STATE;
	cmd.cmdarg = 0;
	cmd.resp_type = MMC_RSP_NONE;
	cmd.flags = 0;

	err = mmc_send_cmd(mmc, &cmd, NULL);

	if (err)
		return err;

	udelay(2000);

	return 0;
}

int mmc_send_ext_csd(struct mmc *mmc, char *ext_csd)
{
	struct mmc_cmd cmd;
	struct mmc_data data;
	int err;

	/* Get the Card Status Register */
	cmd.cmdidx = MMC_CMD_SEND_EXT_CSD;
	cmd.resp_type = MMC_RSP_R1;
	cmd.cmdarg = 0;
	cmd.flags = 0;

	data.dest = ext_csd;
	data.blocks = 1;
	data.blocksize = 512;
	data.flags = MMC_DATA_READ;

	err = mmc_send_cmd(mmc, &cmd, &data);

	return err;
}




void mmc_set_ios(struct mmc *mmc)
{
	mmc->set_ios(mmc);
}

void mmc_set_clock(struct mmc *mmc, uint clock)
{
	if (clock > mmc->f_max)
		clock = mmc->f_max;

	if (clock < mmc->f_min)
		clock = mmc->f_min;

	mmc->clock = clock;

	mmc_set_ios(mmc);
}

void mmc_set_bus_width(struct mmc *mmc, uint width)
{
	mmc->bus_width = width;

	mmc_set_ios(mmc);
}

int mmc_send_if_cond(struct mmc *mmc)
{
	struct mmc_cmd cmd;
	int err;

	cmd.cmdidx = SD_CMD_SEND_IF_COND;
	/* We set the bit if the host supports voltages between 2.7 and 3.6 V */
	cmd.cmdarg = ((mmc->voltages & 0xff8000) != 0) << 8 | 0xaa;
	cmd.resp_type = MMC_RSP_R7;
	cmd.flags = 0;

	err = mmc_send_cmd(mmc, &cmd, NULL);

	if (err)
		return err;
	
	if ((cmd.response[0] & 0xff) != 0xaa)
		return UNUSABLE_ERR;
	else
		mmc->version = SD_VERSION_2;

	return 0;
}
int mmc_app_set_bus_width(struct mmc *mmc, int width)
{
	struct mmc_cmd cmd;
 	int  err;

	 cmd.cmdidx = MMC_CMD_APP_CMD;//put card into app cmd state
	 cmd.resp_type = MMC_RSP_R1;
	 cmd.cmdarg = mmc->rca << 16;
	 cmd.flags = 0;

         err = mmc_send_cmd(mmc, &cmd, NULL);
         if (err)
             return err;

         cmd.cmdidx = SD_CMD_APP_SET_BUS_WIDTH;
         cmd.resp_type = MMC_RSP_R1;
         cmd.cmdarg = 2;
         cmd.flags = 0;
         return  mmc_send_cmd(mmc, &cmd, NULL);
        
}
int mmc_get_sector_count(struct mmc *mmc)
{
       /****************To Do****************/
      if(mmc->initflag==MMC_INITIATED && mmc->sector_count)
              return mmc->sector_count;
       printf("card:%s did not initiated\n",mmc->name);
         return -1;
}
